#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <bolt.h>
#include "tp_debug.h"
#include "tap.h"
#include "pktio.h"
#include "pkt_pool.h"
#include "pkt_queue.h"

static TAILQ_HEAD(pktio_head, pktio) pktio_head = TAILQ_HEAD_INITIALIZER(pktio_head);

static struct pktio pktio_pool[PKTIO_CONFIG_MAX];

struct pktio *get_pktio(int id)
{
  return (valid_pktio_id(id) && pktio_pool[id].use) ? &pktio_pool[id] : NULL;
}


static uint32_t pktio_get_address(const char *ifname)
{
  struct ifreq if_str;
  int fd = socket (PF_INET, SOCK_DGRAM, 0);
  if (fd < 0)
    return 0;
  
  memset (&if_str, 0, sizeof (struct ifreq));
  strcpy (if_str.ifr_name, ifname);
  int ret = ioctl (fd, SIOCGIFADDR, &if_str);
  
  struct sockaddr_in *addr_in = (struct sockaddr_in *)(&if_str.ifr_addr);
  
  if (ret != 0 || addr_in->sin_family != AF_INET) 
    return 0;
  uint32_t addr;
  
  memcpy (&addr, &addr_in->sin_addr, sizeof (uint32_t));
  close(fd);
  return ntohl(addr);
}

static uint32_t pktio_get_netmask(const char *ifname)
{
  struct ifreq if_str;
  int fd = socket (PF_INET, SOCK_DGRAM, 0);
  if (fd < 0)
    return 0;
  
  memset (&if_str, 0, sizeof (struct ifreq));
  strcpy (if_str.ifr_name, ifname);
  int ret = ioctl (fd, SIOCGIFNETMASK, &if_str);
  struct sockaddr_in *addr_in = (struct sockaddr_in *)(&if_str.ifr_addr);
  if (ret != 0 || addr_in->sin_family != AF_INET) 
    return 0;
  uint32_t addr;
  memcpy (&addr, &addr_in->sin_addr, sizeof (uint32_t));
  close(fd);
  return ntohl(addr);
}

static int pktio_get_prefix(uint32_t mask)
{
  int i, prefix=0;
  for (i = 32; i > 0; i--) {
    if (mask & (1 << (i-1)))
      prefix++;
    else
      break;
  }
  return prefix;
}

bool pktio_send(int id, struct llbuf *ll)
{
  struct pktio *p = get_pktio(id);
  if (p && p->fd) {
    write(p->fd, ll->data + ll->offset, ll->size);
    pkt_pool_free(ll->pool, ll);
    return true;
  } else
    return false;
}

static void pktio_event(void *userdata)
{
  struct pktio *p = (struct pktio *)userdata;
  struct llbuf *ll = pkt_pool_alloc(p->pool);
  if (ll == NULL)
    return;
  ll->size = read (p->fd, (void *) &(ll->data), MAX_LL_DATA);
  if (ll->size <= 0) {
    pkt_pool_free(ll->pool, ll);
    return;
  }
  ll->l2_ptr = &ll->data[0];
  ll->l2_size = ll->size;
  ll->offset = 0;
  if (p->rxq < 0 || !pkt_event_send(p->rxq, ll))
    pkt_pool_free(ll->pool, ll);
}



static void pktio_add_event(struct pktio *p)
{
  struct el_events event = {        
    .in_event = pktio_event,        
    .out_event = NULL,      
  };      
  p->handle = el_add(p->fd, &event, p);
}

static void pktio_del_event(struct pktio *p)
{
  if (p->handle) {
    el_rm(p->handle);
    p->handle = NULL;
  }
}

static int pktio_add_tap(int id)
{
  if (!valid_pktio_id(id))
    return -1;
  struct pktio *p = &pktio_pool[id];
  if (!p->use || p->fd >= 0)
    return -1;
  p->fd = tap_open (p->tap_name);
  if (p->fd < 0){
    printf ("open tap failure\n");
    return -1;
  }
  return 0;
}

static int pktio_get_mtu(const char *ifname)
{
  struct ifreq if_str;
  int fd = socket (PF_INET, SOCK_DGRAM, 0);
  memset (&if_str, 0, sizeof (struct ifreq));
  if (ifname[0] == 0)
    return -1;
  strcpy (if_str.ifr_name, ifname);
  int ret = ioctl (fd, SIOCGIFMTU, &if_str);
  if (ret < 0) 
    return ret;
  return if_str.ifr_mtu;
}


bool pktio_create(int id, char *ifname)
{
  char addrbuf[INET_ADDRSTRLEN + 1];
  if (valid_pktio_id(id)) {
    struct pktio *p = &pktio_pool[id];
    if (p->use)
      return false;
    memset(p, 0, sizeof(struct pktio));
    p->mtu = pktio_get_mtu(ifname);
    if (p->mtu == -1)
      return false;
    p->id = id;
    p->fd = -1;
    p->rxq = -1;
    p->txq = -1;
    p->pool = -1;
    strncpy(p->ifname, ifname, MAX_NAME_LEN - 1);
    p->mask = pktio_get_netmask(ifname);
    p->addr = pktio_get_address(ifname);
    if (p->mask != 0 && p->addr != 0) {
      ip2str(p->addr, addrbuf);
      int prefix = pktio_get_prefix(p->mask);
      execute_command ("ip addr del %s/%d dev %s", addrbuf, prefix, ifname);
    }
    p->use = 1;    
    TAILQ_INSERT_TAIL(&pktio_head, p, entry);
    pktio_add_tap(id);
    pktio_add_event(p);
    return true;
  }
  return false;
}

void pktio_destroy(struct pktio *p)
{
  char addrbuf[INET_ADDRSTRLEN + 1],maskbuf[INET_ADDRSTRLEN + 1]; 
  if (p->use > 0){
    if (p->fd) {
      close(p->fd);
      p->fd = -1;
    }
    if (p->addr != 0 && p->mask != 0) {
      ip2str(p->addr, addrbuf);
      ip2str(p->mask, maskbuf);
      execute_command ("ifconfig %s %s netmask %s\n", p->ifname, addrbuf, maskbuf);
    }
    pktio_del_event(p);
    TAILQ_REMOVE(&pktio_head, p, entry);
    p->use = 0;
  }
}

void pktio_init(void)
{
  int i;
  for (i = 0; i < PKTIO_CONFIG_MAX; i++) {
    struct pktio *p = &pktio_pool[i];
    memset(p, 0, sizeof(struct pktio));
  }
}

void pktio_deinit(void)
{
  while (!TAILQ_EMPTY(&pktio_head)) {
    struct pktio * p = TAILQ_FIRST(&pktio_head);
    TAILQ_REMOVE(&pktio_head, p, entry);
    pktio_destroy(p);
  }
}

BOLT(pktio_create, "pktio <id> create <dev>")
{
  uint32_t id;
  char name[MAX_NAME_LEN];
  const char *argz = pop_argz();
  if (argz == NULL || argz[0] == 0 || strlen(argz) >= MAX_NAME_LEN) {
    printf ("invalid name\n");
    return;
  } 
  strncpy(name, argz, MAX_NAME_LEN - 1);
  argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  if (pktio_create(id, name)) 
    printf ("success\n");
  else
    printf ("failure\n");  
}

BOLT(pktio_rxq, "pktio <id> rxq <rxq-id>")
{
  uint32_t id,rxq;
  const char *argz = pop_argz();
  if (!str2u32(argz, &rxq)) {
    printf ("invalid rxq\n");
    return;
  } 
  argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  struct pktio * p = get_pktio(id);
  if (p && valid_pkt_queue_id(rxq)) {
    p->rxq = rxq ;
    printf ("success\n");
  } else
    printf ("failure\n");  
}

BOLT(pktio_txq, "pktio <id> txq <rxq-id>")
{
  uint32_t id,txq;
  const char *argz = pop_argz();
  if (!str2u32(argz, &txq)) {
    printf ("invalid txq\n");
    return;
  } 
  argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  struct pktio * p = get_pktio(id);
  if (p && valid_pkt_queue_id(txq)) {
    p->txq = txq ;
    printf ("success\n");
  } else
    printf ("failure\n");  
}

BOLT(pktio_pool, "pktio <id> pool <pool-id>")
{
  uint32_t id,pool;
  const char *argz = pop_argz();
  if (!str2u32(argz, &pool)) {
    printf ("invalid txq\n");
    return;
  } 
  argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  struct pktio * p = get_pktio(id);
  if (p && valid_pkt_pool_id(pool)) {
    p->pool = pool ;
    printf ("success\n");
  } else
    printf ("failure\n");  
}


static void pktio_show(struct pktio *p)
{
  char addrbuf[INET_ADDRSTRLEN + 1],maskbuf[INET_ADDRSTRLEN + 1]; 

  ip2str(p->addr, addrbuf);
  ip2str(p->mask, maskbuf);

  printf ("%2d %4d %4d %3s %5s %8s %3d %3d %s/%s\n", p->id, p->fd, p->mtu, 
    p->handle != NULL ? "yes" : "no", 
    p->tap_name, p->ifname, p->rxq, p->txq,
    addrbuf, maskbuf);
}

BOLT(pktio_show, "pktio show")
{
  struct pktio *lookup;
  printf ("%2s %4s %4s %3s %5s %8s %3s %3s %s\n", 
    "id", "fd", "mtu", "ev", "tap", "ifname", "rxq", "txq", "address");
  TAILQ_FOREACH(lookup, &pktio_head, entry) {
    pktio_show(lookup);
  }
}

BOLT_PROFILE(pktio,"pool,queue",pktio_profile, desc)
{
  struct pktio *p;
  TAILQ_FOREACH (p, &pktio_head, entry) {
    cmd_load_profile(desc, "pktio %d create %s", p->id, p->ifname);
    if (p->rxq >= 0)
      cmd_load_profile(desc, "pktio %d rxq %d", p->id, p->rxq);
    if (p->txq >= 0)
      cmd_load_profile(desc, "pktio %d txq %d", p->id, p->txq);
    if (p->pool >= 0)
      cmd_load_profile(desc, "pktio %d pool %d", p->id, p->pool);
  }
  cmd_load_profile(desc,"\n");
}

void llbuf_print(struct llbuf *ll)
{
  int i;
  for (i=0;i<ll->size;i++){
    printf("%02x ", ll->data[i]);
    if (((i + 1) % 16) == 0)
      printf("\n");
  }
  printf("\n");
}
